package com.yzc.controller;

import com.yzc.Details.SecurityUser;
import com.yzc.result.Result;
import com.yzc.util.JwtUtil;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.validation.Valid;
import jakarta.validation.constraints.NotBlank;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.core.script.DefaultRedisScript;
import org.springframework.data.redis.core.script.RedisScript;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.authentication.BadCredentialsException;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.Authentication;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.concurrent.TimeUnit;

import static com.yzc.result.ResultCodeEnum.FAIL;
import static com.yzc.result.ResultCodeEnum.USERNAME_OR_PASSWORD_ERROR;

@Slf4j
@RestController
@RequestMapping("/securityLogin")
@RequiredArgsConstructor
public class SecurityLoginController {

    private final AuthenticationManager authenticationManager;
    private final JwtUtil jwtUtil;
    private final RedisTemplate<String, String> redisTemplate;

    @PostMapping("/login")
    public Result<SecurityLoginResponse> login(
            @Valid @RequestBody SecurityLoginRequest request, HttpServletRequest httpRequest) {
        try {
            Authentication authentication = authenticationManager.authenticate(
                    new UsernamePasswordAuthenticationToken(request.getUsername(), request.getPassword())
            );

            SecurityUser securityUser = (SecurityUser) authentication.getPrincipal();
            String token = jwtUtil.generateSecurityToken(securityUser);
            String tokenKey = "token:" + token;

            // 存储Hash结构
            Map<String, String> tokenDetails = new HashMap<>();
            tokenDetails.put("user_id", securityUser.getId().toString());
            tokenDetails.put("username", securityUser.getUsername());
            tokenDetails.put("rolename", securityUser.getRolename());
            tokenDetails.put("device_info", httpRequest.getHeader("User-Agent"));
            tokenDetails.put("ip", httpRequest.getRemoteAddr());
            tokenDetails.put("created_at", String.valueOf(System.currentTimeMillis()));
            redisTemplate.opsForHash().putAll(tokenKey, tokenDetails);

            long expiresInSeconds = jwtUtil.getExpirationMillis() / 1000;
            redisTemplate.expire(tokenKey, expiresInSeconds, TimeUnit.SECONDS);

            // 使用Lua脚本管理用户Token数量
            String userTokenKey = "user_tokens:" + securityUser.getId();
            long timestamp = System.currentTimeMillis();
            int maxTokens = 3;

            String script =
                    "local userTokenKey = KEYS[1]\n" +
                            "local newToken = ARGV[1]\n" +
                            "local timestamp = ARGV[2]\n" +
                            "local maxTokens = tonumber(ARGV[3])\n" +
                            "redis.call('ZADD', userTokenKey, timestamp, newToken)\n" +
                            "local count = redis.call('ZCARD', userTokenKey)\n" +
                            "local removedTokens = {}\n" +
                            "if count > maxTokens then\n" +
                            "    local removeCount = count - maxTokens\n" +
                            "    removedTokens = redis.call('ZRANGE', userTokenKey, 0, removeCount - 1)\n" +
                            "    redis.call('ZREMRANGEBYRANK', userTokenKey, 0, removeCount - 1)\n" +
                            "    for _, token in ipairs(removedTokens) do\n" +
                            "        redis.call('DEL', 'token:'..token)\n" +
                            "    end\n" +
                            "end\n" +
                            "return removedTokens";

            RedisScript<List> redisScript = new DefaultRedisScript<>(script, List.class);
            List<String> removedTokens = redisTemplate.execute(redisScript, Collections.singletonList(userTokenKey), token, String.valueOf(timestamp), String.valueOf(maxTokens));

            log.debug("Removed old tokens: {}", removedTokens);

            return Result.success(new SecurityLoginResponse(token, "Bearer", expiresInSeconds));

        } catch (BadCredentialsException e) {
            log.warn("用户 {} 登录失败: 用户名或密码错误", request.getUsername());
            return Result.fail(USERNAME_OR_PASSWORD_ERROR);
        } catch (Exception e) {
            log.error("用户 {} 登录时发生异常", request.getUsername(), e);
            return Result.fail(FAIL);
        }
    }

    @PostMapping("/logout")
    public Result<Void> logout(HttpServletRequest request) {
        String token = jwtUtil.resolveToken(request.getHeader("Authorization"));
        if (token != null) {
            String tokenKey = "token:" + token;
            Map<Object, Object> tokenDetails = redisTemplate.opsForHash().entries(tokenKey);
            if (tokenDetails != null && !tokenDetails.isEmpty()) {
                String userId = (String) tokenDetails.get("user_id");
                // 删除Token的Hash存储
                redisTemplate.delete(tokenKey);
                // 从用户Token集合中移除
                String userTokenKey = "user_tokens:" + userId;
                redisTemplate.opsForZSet().remove(userTokenKey, token);
            }
        }
        return Result.success();
    }

    // --- 内部请求/响应类 ---
    @Data
    public static class SecurityLoginRequest {
        @NotBlank(message = "用户名不能为空")
        private String username;
        @NotBlank(message = "密码不能为空")
        private String password;
    }

    @Data
    @AllArgsConstructor
    public static class SecurityLoginResponse {
        private String token;
        private String tokenType;  // 明确令牌类型（如 Bearer）
        private long expiresIn;    // 过期时间（秒）
    }
}